#include "mainwindow.h"
#include <QMessageBox>   // 用于显示消息框
#include <QFileInfo>     // 用于获取文件名
#include <QDataStream>   // 用于数据序列化
#include <QHostAddress>  // 用于处理IP地址

/**
 * @brief “发送文件”按钮点击事件处理槽函数。
 *
 * 验证文件是否已选择、目标IP和端口是否有效。
 * 如果已在进行接收操作，会先清理接收状态。
 * 初始化文件发送相关的成员变量（如总包数、当前包序号）。
 * 打开选定的文件准备读取，并启动发送过程（发送第一个包并启动超时计时器）。
 * 更新UI以反映发送状态，并启动速度更新计时器。
 */
void MainWindow::on_btnSend_clicked()
{
    if (filePath.isEmpty()) {
        QMessageBox::warning(this, "提示", "请先选择文件！");
        return;
    }
    if (receivingFileActive) cleanupReceiverState(false, true); // 取消可能正在进行的接收

    if (file) { // 清理上一个发送的文件对象
        if(file->isOpen()) file->close();
        delete file;
        file = nullptr;
    }
    file = new QFile(filePath); // 为新选择的文件创建对象

    if (!file->open(QIODevice::ReadOnly)) {
        QMessageBox::warning(this, "错误", "文件打开失败: " + file->errorString());
        labelFileSize->setText("文件大小: 打开失败");
        delete file; file = nullptr;
        return;
    }
    labelFileSize->setText(QString("文件大小: %1").arg(formatFileSize(file->size())));

    if (file->size() == 0) {
        QMessageBox::warning(this, "提示", "选择的文件为空，无法发送。");
        if(file->isOpen()) file->close(); delete file; file = nullptr;
        return;
    }

    totalPackets = (file->size() + packetSize - 1) / packetSize;
    currentPacket = 0;
    targetIP = editTargetIP->text();
    targetPort = editTargetPort->text().toUShort();

    if (targetIP.isEmpty() || targetPort == 0) {
        QMessageBox::warning(this, "提示", "请输入有效的目标IP和端口！");
        if(file->isOpen()) file->close(); delete file; file = nullptr;
        return;
    }

    sendingFileActive = true;
    updateUiForActiveTransfer(true);

    progressBar->setMaximum(totalPackets);
    progressBar->setValue(0);
    textLog->append(QString("开始发送文件 '%1' 到 %2:%3").arg(QFileInfo(filePath).fileName()).arg(targetIP).arg(targetPort));
    textLog->append("总包数: " + QString::number(totalPackets));

    bytesSentInCurrentInterval = 0;
    transferDurationTimer.start();
    speedUpdateTimer->start(1000);
    labelTransferSpeed->setText("速度: 计算中...");

    sendPacket(currentPacket);
    timer->start(1000); // 重传计时器
}

/**
 * @brief 发送指定序号的数据包。
 *
 * 从文件中读取对应包序号的数据块，构建数据包（包含包序号和总包数头部），
 * 并通过UDP套接字发送到目标地址和端口。
 * 如果发送状态无效或文件未打开，则不执行任何操作。
 *
 * @param seq 要发送的数据包的序号（从0开始）。
 */
void MainWindow::sendPacket(quint32 seq)
{
    if (!sendingFileActive || !file || !file->isOpen()) {
        if (timer->isActive()) timer->stop();
        if (speedUpdateTimer->isActive()) speedUpdateTimer->stop();
        return;
    }

    QByteArray datagram;
    QDataStream out(&datagram, QIODevice::WriteOnly);
    out.setVersion(QDataStream::Qt_5_15);
    out << quint32(seq) << quint32(totalPackets); // 包序号和总包数

    qint64 start = (qint64)seq * packetSize;
    qint64 len = qMin((qint64)packetSize, file->size() - start);

    file->seek(start);
    QByteArray data = file->read(len);
    datagram.append(data);

    udpSocket->writeDatagram(datagram, QHostAddress(targetIP), targetPort);
    textLog->append(QString("发送第 %1/%2 包 (数据大小: %3 bytes)").arg(seq + 1).arg(totalPackets).arg(datagram.size()));
}

/**
 * @brief 处理接收到的ACK确认包（发送端逻辑的一部分）。
 *
 * 此函数在`onReadyRead`检测到ACK包时被调用。
 * 解析ACK包中的序列号。如果ACK有效且对应当前发送的包：
 * - 停止当前包的重传计时器。
 * - 更新进度条和日志。
 * - 累加已确认的字节数用于速度计算。
 * - 递增当前包序号，如果还有未发送的包，则发送下一个包并启动其重传计时器。
 * - 如果所有包都已发送并确认，则标记发送完成并清理状态。
 * 如果ACK无效或序列号不匹配，则记录日志。
 *
 * @param ackDatagram 收到的包含ACK信息的原始数据报。
 * @param sender 发送ACK包的主机地址。
 * @param senderPort 发送ACK包的端口号。
 */
void MainWindow::handleAckPacket(const QByteArray &ackDatagram, const QHostAddress &sender, quint16 senderPort)
{
    // 此函数由 onReadyRead 调用，已在 onReadyRead 中检查了 sendingFileActive
    bool ok;
    quint32 ackSeq = ackDatagram.mid(4).toUInt(&ok); // "ACK:" 是4个字符，后面是序号

    if (ok && ackSeq == currentPacket) { // ACK有效且是期望的包
        timer->stop(); // 停止该包的重传计时器
        progressBar->setValue(currentPacket + 1);
        textLog->append(QString("收到第 %1 包的 ACK 从 %2:%3").arg(currentPacket + 1).arg(sender.toString()).arg(senderPort));

        // 计算已确认的数据量用于速度统计
        qint64 dataPayloadSize = 0;
        if (ackSeq < totalPackets - 1) { // 不是最后一个包
            dataPayloadSize = packetSize;
        } else { // 是最后一个包
            dataPayloadSize = file->size() - (qint64)(totalPackets - 1) * packetSize;
        }
        if (dataPayloadSize < 0) dataPayloadSize = 0; // 安全检查
        bytesSentInCurrentInterval += dataPayloadSize;

        currentPacket++; // 移至下一个包
        if (currentPacket < totalPackets) { // 如果还有包未发送
            sendPacket(currentPacket); // 发送下一个包
            timer->start(1000); // 为下一个包启动重传计时器
        } else { // 所有包都已发送并收到确认
            textLog->append(QString("文件 '%1' 发送完成！").arg(QFileInfo(filePath).fileName()));
            cleanupSenderState(true); // 清理发送状态，标记为成功
        }
    } else if (ok) { // ACK有效但包序号不对 (乱序或过时)
        textLog->append(QString("收到乱序或过时的 ACK (期望 %1, 收到 %2) 从 %3:%4").arg(currentPacket + 1).arg(ackSeq + 1).arg(sender.toString()).arg(senderPort));
    } else { // ACK无法解析
        textLog->append(QString("收到无法解析的 ACK: %1 从 %2:%3").arg(QString(ackDatagram.left(20))).arg(sender.toString()).arg(senderPort));
    }
}


/**
 * @brief 发送端重传计时器超时处理槽函数。
 *
 * 当等待某个数据包的ACK超时后，此函数被调用。
 * 它会重发当前未收到ACK的数据包，并重启重传计时器。
 * 如果发送操作已结束或不活跃，则停止计时器。
 */
void MainWindow::onTimeout()
{
    if (!sendingFileActive || currentPacket >= totalPackets) {
        if (timer->isActive()) timer->stop();
        return;
    }
    textLog->append(QString("第 %1 包发送超时，正在重发...").arg(currentPacket + 1));
    sendPacket(currentPacket); // 重发当前包
    timer->start(1000); // 重启计时器
}

/**
 * @brief 发送端速度更新显示处理槽函数。
 *
 * 此函数由`speedUpdateTimer`定时触发。
 * 它使用在过去一个时间间隔内确认发送的字节数（`bytesSentInCurrentInterval`）
 * 和时间间隔（1秒）来计算并显示当前的传输速度。
 * 计算后重置`bytesSentInCurrentInterval`以便下一个间隔的计算。
 * 如果发送不活跃或相关条件不满足，则不执行更新。
 */
void MainWindow::updateTransferSpeedDisplay()
{
    if (!sendingFileActive || !transferDurationTimer.isValid() || !file || file->size() == 0 || !speedUpdateTimer->isActive()) {
        return;
    }
    labelTransferSpeed->setText(QString("速度: %1").arg(formatSpeed(bytesSentInCurrentInterval, 1000))); // 1000ms = 1s
    bytesSentInCurrentInterval = 0; // 为下一个间隔重置
}